/**
  ******************************************************************************
  * @文件     spiflash.c
  * @作者     hero_cao
  * @版本     V1.0.0
  * @日期     2025/06/14
  * @概要     spiflash驱动
  ******************************************************************************
  */
  
/* Includes ------------------------------------------------------------------*/
#include "./spi/spi.h"
#include "./spiflash/spiflash.h"
#include "at32f403a_407.h"


/* Private macros ------------------------------------------------------------*/
#define FLASH_GETID				0x90
#define FLASH_CMD_WREN			0x06
#define FLASH_CMD_ERASE_SECTOR	0x20
#define FLASH_CMD_ERASE_CHIP	0xC7
#define FLASH_CMD_WRITEPAGE		0x02
#define FLASH_CMD_READPAGE		0x03
#define FLASH_CMD_READSTS		0x05
#define FLASH_CS_L()			gpio_bits_reset(GPIOB, GPIO_PINS_12)
#define FLASH_CS_H()			gpio_bits_set(GPIOB, GPIO_PINS_12)

/* Private variables ---------------------------------------------------------*/
uint32_t spi_flash_size = 0x400000;


/**
  * @brief  初始化外部spiflash.
  * @param  无
  * @retval 0-成功,其他-失败
  */
uint32_t flash_spi_init(void)
{
	uint32_t device_id = 0;
	
	spi2_init();
  
	device_id = flash_spi_getid();
	if ( device_id == 0x0115 ) {
		spi_flash_size = 0x400000; /*4m byte*/
	} else if ( device_id == 0xc216 ) {
		spi_flash_size = 0x800000; /*8m byte*/
	} else {
		spi_flash_size = 0;
		return 1;
	}
	return 0;
}

/**
  * @brief  等待空闲
  * @param  无
  * @retval 无
  */
static void flash_spi_wait_busy(void)
{
    uint8_t flashstatus = 0;

    FLASH_CS_L();
    spi2_read_write_byte(FLASH_CMD_READSTS);

    do {
        flashstatus = spi2_read_write_byte(0xff);
    } while((flashstatus & 0x1) == SET);
    FLASH_CS_H();
}

/**
 * @brief       spiflash写使能
 *   @note      将S1寄存器的WEL置位
 * @param       无
 * @retval      无
 */
void flash_spi_write_enable(void)
{
	FLASH_CS_L();
    spi2_read_write_byte(FLASH_CMD_WREN);   /* 发送写使能 */
	FLASH_CS_H();
}

/**
 * @brief       spiflash发送地址
 * @param       address : 要发送的地址
 * @retval      无
 */
static void flash_spi_send_address(uint32_t address)
{ 
    spi2_read_write_byte((uint8_t)((address)>>16));     /* 发送 bit23 ~ bit16 地址 */
    spi2_read_write_byte((uint8_t)((address)>>8));      /* 发送 bit15 ~ bit8  地址 */
    spi2_read_write_byte((uint8_t)address);             /* 发送 bit7  ~ bit0  地址 */
}

/**
 * @brief       读取芯片ID
 * @param       无
 * @retval      FLASH芯片ID
 */
uint32_t flash_spi_getid(void)
{
	uint16_t deviceid;

	FLASH_CS_L();
    spi2_read_write_byte(FLASH_GETID);				/* 发送读 ID 命令 */
    spi2_read_write_byte(0);                        /* 写入一个字节 */
    spi2_read_write_byte(0);
    spi2_read_write_byte(0);
    deviceid = spi2_read_write_byte(0xFF) << 8;     /* 读取高8位字节 */
    deviceid |= spi2_read_write_byte(0xFF);         /* 读取低8位字节 */
	FLASH_CS_H();

    return deviceid;
}

/**
 * @brief       读取SPI FLASH
 *   @note      在指定地址开始读取指定长度的数据
 * @param       pbuf    : 数据存储区
 * @param       addr    : 开始读取的地址(最大32bit)
 * @param       datalen : 要读取的字节数(最大65535)
 * @retval      无
 */
void flash_spi_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    uint16_t i;
	
	FLASH_CS_L();
    spi2_read_write_byte(FLASH_CMD_READPAGE);	/* 发送读取命令 */
    flash_spi_send_address(addr);				/* 发送地址 */
    
    for (i = 0; i < datalen; i++) {
        pbuf[i] = spi2_read_write_byte(0XFF);	/* 循环读取 */
    }
    FLASH_CS_H();
}

/**
 * @brief       SPI在一页(0~65535)内写入少于256个字节的数据
 *   @note      在指定地址开始写入最大256字节的数据
 * @param       pbuf    : 数据存储区
 * @param       addr    : 开始写入的地址(最大32bit)
 * @param       datalen : 要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
 * @retval      无
 */
static void flash_spi_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    uint16_t i;

    flash_spi_write_enable();					/* 写使能 */

	FLASH_CS_L();
    spi2_read_write_byte(FLASH_CMD_WRITEPAGE);	/* 发送写页命令 */
    flash_spi_send_address(addr);				/* 发送地址 */

    for (i = 0; i < datalen; i++) {
        spi2_read_write_byte(pbuf[i]);          /* 循环写入 */
    }
	FLASH_CS_H();
    
    flash_spi_wait_busy();						/* 等待写入结束 */
}

/**
 * @brief       无检验写SPI FLASH
 *   @note      必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
 *              具有自动换页功能
 *              在指定地址开始写入指定长度的数据,但是要确保地址不越界!
 *
 * @param       pbuf    : 数据存储区
 * @param       addr    : 开始写入的地址(最大32bit)
 * @param       datalen : 要写入的字节数(最大65535)
 * @retval      无
 */
static void flash_spi_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    uint16_t pageremain;
    pageremain = 256 - addr % 256;		/* 单页剩余的字节数 */

    if (datalen <= pageremain) {		/* 不大于256个字节 */
        pageremain = datalen;
    }

    while (1) {
        /* 当写入字节比页内剩余地址还少的时候, 一次性写完
         * 当写入直接比页内剩余地址还多的时候, 先写完整个页内剩余地址, 然后根据剩余长度进行不同处理
         */
        flash_spi_write_page(pbuf, addr, pageremain);
        if (datalen == pageremain) {	/* 写入结束了 */   
            break;
        } else {						/* datalen > pageremain */  
            pbuf += pageremain;         /* pbuf指针地址偏移,前面已经写了pageremain字节 */
            addr += pageremain;         /* 写地址偏移,前面已经写了pageremain字节 */
            datalen -= pageremain;      /* 写入总长度减去已经写入了的字节数 */
            if (datalen > 256) {		/* 剩余数据还大于一页,可以一次写一页 */
                pageremain = 256;       /* 一次可以写入256个字节 */
            } else {                    /* 剩余数据小于一页,可以一次写完 */  
                pageremain = datalen;   /* 不够256个字节了 */
            }
        }
    }
}

/**
 * @brief       擦除整个芯片
 *   @note      等待时间超长...
 * @param       无
 * @retval      无
 */
void flash_spi_erase_chip(void)
{
    flash_spi_write_enable();    /* 写使能 */
    flash_spi_wait_busy();       /* 等待空闲 */
	FLASH_CS_L();
    spi2_read_write_byte(FLASH_CMD_ERASE_CHIP);  /* 发送读寄存器命令 */
	FLASH_CS_H();
    flash_spi_wait_busy();       /* 等待芯片擦除结束 */
}

/**
 * @brief       擦除一个扇区
 *   @note      注意,这里是扇区地址,不是字节地址!!
 *              擦除一个扇区的最少时间:150ms
 *
 * @param       saddr : 扇区地址 根据实际容量设置
 * @retval      无
 */
void flash_spi_erase_sector(uint32_t saddr)
{
    saddr *= 4096;
    flash_spi_write_enable();        /* 写使能 */
    flash_spi_wait_busy();           /* 等待空闲 */
	FLASH_CS_L();
    spi2_read_write_byte(FLASH_CMD_ERASE_SECTOR);    /* 发送写页命令 */
    flash_spi_send_address(saddr);   /* 发送地址 */
	FLASH_CS_H();
    flash_spi_wait_busy();           /* 等待扇区擦除完成 */
}

/**
 * @brief       写SPI FLASH
 *   @note      在指定地址开始写入指定长度的数据 , 该函数带擦除操作!
 *              SPI FLASH 一般是: 256个字节为一个Page, 4Kbytes为一个Sector, 16个扇区为1个Block
 *              擦除的最小单位为Sector.
 *
 * @param       pbuf    : 数据存储区
 * @param       addr    : 开始写入的地址(最大32bit)
 * @param       datalen : 要写入的字节数(最大65535)
 * @retval      无
 */
uint8_t g_norflash_buf[4096];   /* 扇区缓存 */

void flash_spi_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    uint32_t secpos;
    uint16_t secoff;
    uint16_t secremain;
    uint16_t i;
    uint8_t *norflash_buf;
	
    norflash_buf = g_norflash_buf;
    secpos = addr / 4096;       /* 扇区地址 */
    secoff = addr % 4096;       /* 在扇区内的偏移 */
    secremain = 4096 - secoff;  /* 扇区剩余空间大小 */

    if (datalen <= secremain) {
        secremain = datalen;    /* 不大于4096个字节 */
    }

    while (1) {
        flash_spi_read(norflash_buf, secpos * 4096, 4096);   /* 读出整个扇区的内容 */

        for (i = 0; i < secremain; i++) {	/* 校验数据 */    
            if (norflash_buf[secoff + i] != 0XFF) {
                break;                      /* 需要擦除, 直接退出for循环 */
            }
        }

        if (i < secremain) {				/* 需要擦除 */ 
            flash_spi_erase_sector(secpos);	/* 擦除这个扇区 */
            for (i = 0; i < secremain; i++) {     
                norflash_buf[i + secoff] = pbuf[i];
            }

            flash_spi_write_nocheck(norflash_buf, secpos * 4096, 4096);  /* 写入整个扇区 */
        } else {   							/* 写已经擦除了的,直接写入扇区剩余区间. */
            flash_spi_write_nocheck(pbuf, addr, secremain);              /* 直接写扇区 */
        }

        if (datalen == secremain) {
            break;		/* 写入结束了 */
        } else {		/* 写入未结束 */
            secpos++;               /* 扇区地址增1 */
            secoff = 0;             /* 偏移位置为0 */

            pbuf += secremain;      /* 指针偏移 */
            addr += secremain;      /* 写地址偏移 */
            datalen -= secremain;   /* 字节数递减 */

            if (datalen > 4096) {
                secremain = 4096;   /* 下一个扇区还是写不完 */
            } else {
                secremain = datalen;/* 下一个扇区可以写完了 */
            }
        }
    }
	
}

